package cn.ligoo.dtu.server.codec;

import java.math.BigDecimal;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang3.StringUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.ligoo.dtu.domain.BmuMasterInfo;
import cn.ligoo.dtu.domain.BmuSlaveBalanceCurrent;
import cn.ligoo.dtu.domain.BmuSlaveBatteryState;
import cn.ligoo.dtu.domain.BmuSlaveBoxTemp;
import cn.ligoo.dtu.domain.BmuSlaveInfo;
import cn.ligoo.dtu.domain.DtuInfo;
import cn.ligoo.util.ByteUtils;
import cn.ligoo.util.CRC16M;

/**
 * <pre>
 * flip:
 * 		set limit to current position
 * 		set position to 0
 * clear:
 * 		set limit to capacity
 * 		set position to 0
 * </pre>
 * 
 * @author QingHua.Jin
 * 
 */
public class DtuDecoder extends CumulativeProtocolDecoder {
	public static final String START_CODE = "LIGOO";
	public static final int HEART_BEAT = 0xfe;

	private final Logger logger = LoggerFactory.getLogger(DtuDecoder.class);
	private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
	private final Charset charset;

	public DtuDecoder(Charset charset) {
		this.charset = charset;
	}

	@Override
	protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
		logger.debug("......doDecode");
		in.order(ByteOrder.LITTLE_ENDIAN);
		CharsetDecoder cd = charset.newDecoder();
		CharsetEncoder ce = charset.newEncoder();
		logger.debug("--->in.limit:{};--->in.remaining:{};--->in.position:{}",
				new Object[] { in.limit(), in.remaining(), in.position() });

		Context ctx = getContext(session);

		// 把in的数据全部读到ctx.in中，过滤心跳包及完成反转义
		// 0xfe -> 心跳包
		// 0xfd -转义-> 0xfd 0xed -反转义-> 0xfd
		// 0xfe -转义-> 0xfd 0xee -反转义-> 0xfe
		// byte pre_b = 0;
		while (in.hasRemaining()) {
			byte cur_b = in.get();
			int cur_i = cur_b & 0xff;
			// if (cur_i == 0xfe) {
			// logger.info("{}===0xfe[HEATBEAT]", Integer.toHexString(cur_i));
			// continue;
			// }
			// if (cur_i == 0xfd) {
			// logger.info("{}===0xfd", Integer.toHexString(cur_i));
			// if (in.hasRemaining()) {
			// byte nxt_b = in.get();
			// int nxt_i = nxt_b & 0xff;
			// if (nxt_i == 0xed) {
			// logger.info("{}===0xed", Integer.toHexString(nxt_i));
			// ctx.in.put((byte) 0xfd);
			// } else if (nxt_i == 0xee) {
			// logger.info("{}===0xee", Integer.toHexString(nxt_i));
			// ctx.in.put((byte) 0xfe);
			// } else {
			// ctx.in.put(cur_b);
			// ctx.in.put(nxt_b);
			// }
			// } else {
			// return false;
			// }
			// } else {
			// logger.debug("{}", Integer.toHexString(cur_i));
			// ctx.in.put(cur_b);
			// }
			logger.debug("{}", Integer.toHexString(cur_i));
			ctx.in.put(cur_b);

			if (ctx.isFirstFrame()) {
				if (ctx.in.position() < 21) {
					continue;
				}
				DtuInfo dtuInfo = new DtuInfo();

				ctx.in.flip();
				byte[] modemIdBytes = new byte[4];
				for (int i = 3; i >= 0; i--) {
					modemIdBytes[i] = ctx.in.get();
				}
				ctx.setModemId(Integer.parseInt(ByteUtils.bytesToHexString(modemIdBytes)));
				dtuInfo.setModem_id(ctx.getModemId());
				logger.info("...modem_id:{}", dtuInfo.getModem_id());

				String phone_no = ctx.in.getString(12, cd);
				logger.info("...phone_no:{}", phone_no);

				if (!StringUtils.isNumeric(phone_no) || !"1".equals(phone_no.substring(0, 1))) {
					logger.warn("!!!data is exceptional");
					session.close(true);
					return true;
				}
				dtuInfo.setPhone_no(phone_no);

				Integer[] ips = new Integer[4];
				for (int i = 0; i < ips.length; i++) {
					ips[i] = ctx.in.get() & 0xff;
				}
				dtuInfo.setDyn_ip(StringUtils.join(ips, "."));
				ctx.in.get();
				dtuInfo.setConn_time(new Date());
				dtuInfo.setRefresh_time(new Date());
				dtuInfo.setIs_online(new Byte("1"));
				dtuInfo.setIs_del(new Byte("0"));

				out.write(dtuInfo);
				ctx.setFirstFrame(false);
				ctx.in.clear();
				return true;
			}

			if (ctx.getLength() == 0) {
				if (ctx.in.position() < 6) {
					// logger.debug("...数据包小于{}，不处理，再接收", 6);
					continue;
				}
				ctx.in.flip();
				String startCode = null;
				try {
					startCode = ctx.in.getString(5, cd);
					if (!DtuDecoder.START_CODE.equals(startCode)) {
						logger.error("...start code[{}] is not 'LIGOO'", startCode);
						session.close(true);
						return true;
					}
				} catch (Exception ex) {
					ex.printStackTrace();
					session.close(true);
					return true;
				}
				int length = ctx.in.get() & 0xff;
				ctx.in.clear();
				ctx.setStartCode(startCode);
				ctx.setLength(length);
				logger.debug("...startCode:{};length:{}", startCode, length);
				ctx.crc.clear();
				ctx.crc.putString(ctx.getStartCode(), ce);
				ctx.crc.put((byte) ctx.getLength());
			}

			if (ctx.in.position() < ctx.getLength() - 6) {
				// logger.debug("...数据包小于{}，不处理，再接收", ctx.getLength() - 6);
				continue;
			}

			ctx.in.flip();
			byte ver = ctx.in.get();
			byte num = ctx.in.get();
			byte sid = ctx.in.get();
			byte ctl = ctx.in.get();
			byte tfs = ctx.in.get();
			byte fid = ctx.in.get();
			byte[] dataArray = new byte[ctx.getLength() - 14];
			ctx.in.get(dataArray);

			byte[] crcArray = new byte[2];
			ctx.in.get(crcArray);
			ctx.in.clear();

			ctx.crc.put(ver);
			ctx.crc.put(num);
			ctx.crc.put(sid);
			ctx.crc.put(ctl);
			ctx.crc.put(tfs);
			ctx.crc.put(fid);
			ctx.crc.put(dataArray);
			ctx.crc.put(crcArray);
			ctx.crc.flip();

			byte[] crcBytes = new byte[ctx.getLength()];
			ctx.crc.get(crcBytes);
			ctx.crc.clear();

			if (!CRC16M.checkBuf(crcBytes)) {
				logger.error("CRC check error!");
				ctx.reset();
				return true;
			}
			logger.debug("CRC check ok!");

			if (ver != 0x02) {
				logger.error("decoder only support ver4.02!");
				ctx.reset();
				return true;
			}

			ctx.data.put(dataArray);
			// num 但是在有应答的情况下 要注明序列号
			if (fid < tfs) {
				return false;
			}
			ctx.data.flip();
			// IoBuffer dataBuffer = IoBuffer.allocate(ctx.data.limit());
			// ctx.data.put(ctx.data);
			// ctx.data.flip();

			// logger.debug("...... dataBytes.length:{}", dataBytes.length);
			if (sid == 0x00) { // 主机
				logger.debug("主机............................................................");
				BmuMasterInfo bmi = new BmuMasterInfo();

				// MODEM_ID
				bmi.setModem_id(ctx.getModemId());
				// 从机ID
				bmi.setSid(sid);
				// 数据日期
				bmi.setData_create_time(new Date());
				// 数据版本号
				byte d_0 = ctx.data.get();
				logger.debug("数据版本号={}", d_0 == 0x01 ? "V1.01" : "N/A");
				bmi.setData_version(d_0);
				// SOC(0.4%/bit)
				int d_1 = ctx.data.get() & 0xff;
				logger.debug("SOC={}%", d_1 * 0.4);
				bmi.setSoc(BigDecimal.valueOf(d_1 * 0.4));
				// 电池状态
				int d_234 = ctx.data.getUnsignedMediumInt();
				logger.debug("电池状态={}", Integer.toHexString(d_234));
				bmi.setBatterys_status(d_234);
				// 总容量
				int d_5_6 = ctx.data.getUnsignedShort();
				logger.debug("总容量={}AH", d_5_6);
				bmi.setCapacity_total(d_5_6);
				// 剩余容量
				int d_7_8 = ctx.data.getUnsignedShort();
				logger.debug("剩余容量={}AH", d_7_8);
				bmi.setCapacity_remain(d_7_8);
				// 电池组总电压
				int d_9_10 = ctx.data.getUnsignedShort();
				logger.debug("电池组总电压={}V", d_9_10);
				bmi.setVoltage_total(d_9_10);
				// 电池组总电流
				int d_11_12 = ctx.data.getUnsignedShort();
				logger.debug("电池组总电流={}A", (d_11_12 - 32000) * 0.1);
				bmi.setCurrent_total(BigDecimal.valueOf((d_11_12 - 32000) * 0.1));
				// 电池组充电次数
				int d_13_14 = ctx.data.getUnsignedShort();
				logger.debug("电池组充电次数={}次", d_13_14);
				bmi.setRecharge_cycles(d_13_14);
				// 绝缘正极电阻
				int d_15_16 = ctx.data.getUnsignedShort();
				logger.debug("绝经正极电阻={}KΩ", d_15_16 * 0.1);
				bmi.setResistance_positive(BigDecimal.valueOf((d_15_16) * 0.1));
				// 绝缘负极电阻
				int d_17_18 = ctx.data.getUnsignedShort();
				logger.debug("绝经负极电阻={}KΩ", d_17_18 * 0.1);
				bmi.setResistance_negative(BigDecimal.valueOf((d_17_18) * 0.1));
				// 总绝缘电组
				int d_19_20 = ctx.data.getUnsignedShort();
				logger.debug("总绝经电阻={}KΩ", d_19_20 * 0.1);
				bmi.setResistance_total(BigDecimal.valueOf((d_19_20) * 0.1));
				// 绝缘模块工作状态
				byte d_21 = ctx.data.get();
				logger.debug("绝缘模块工作状态={}", new String[] { "正常", "系统漏电", "绝缘模块离线", "绝缘母线异常" }[d_21]);
				bmi.setResistance_status(d_21);
				// SOH
				int d_22 = ctx.data.getUnsigned();
				logger.debug("SOH={}%", d_22 * 0.4);
				bmi.setSoh(BigDecimal.valueOf(d_22 * 0.4));
				// 最高单体电压
				int d_23_24 = ctx.data.getUnsignedShort();
				logger.debug("最高单体电压={}mv", d_23_24);
				bmi.setBattery_voltage_high(d_23_24);
				// 最低单体电压
				int d_25_26 = ctx.data.getUnsignedShort();
				logger.debug("最低单体电压={}mv", d_25_26);
				bmi.setBattery_voltage_low(d_25_26);
				// 最高箱体温度
				short d_27 = ctx.data.getUnsigned();
				logger.debug("最高箱体温度(偏移)={},(真实)={}℃", d_27, d_27 - 40);
				bmi.setBox_temp_high((short) (d_27 - 40));
				// 最低箱体温度
				short d_28 = ctx.data.getUnsigned();
				logger.debug("最低箱体温度(偏移)={},(真实)={}℃", d_28, d_28 - 40);
				bmi.setBox_temp_low((short) (d_28 - 40));
				// 最高单体电压箱体号
				byte d_29 = ctx.data.get();
				logger.debug("最高单体电压箱体号={}", d_29);
				bmi.setBox_no_voltage_high(d_29);
				// 最高单体电压串号
				byte d_30 = ctx.data.get();
				logger.debug("最高单体电压串号={}", d_30);
				bmi.setSearies_no_voltage_high(d_30);
				// 最低单体电压箱体号
				byte d_31 = ctx.data.get();
				logger.debug("最低单体电压箱体号={}", d_31);
				bmi.setBox_no_voltage_low(d_31);
				// 最低单体电压串号
				byte d_32 = ctx.data.get();
				logger.debug("最低单体电压串号={}", d_32);
				bmi.setSearies_no_voltage_low(d_32);
				// 最高温度箱体号
				byte d_33 = ctx.data.get();
				logger.debug("最高温度箱体号={}", d_33);
				bmi.setBox_no_temp_high(d_33);
				// 最高温度串号
				byte d_34 = ctx.data.get();
				logger.debug("最高温度串号={}", d_34);
				bmi.setSearies_no_temp_high(d_34);
				// 最低温度箱体号
				byte d_35 = ctx.data.get();
				logger.debug("最低温度箱体号={}", d_35);
				bmi.setBox_no_temp_low(d_35);
				// 最低温度串号
				byte d_36 = ctx.data.get();
				logger.debug("最低温度串号={}", d_36);
				bmi.setSearies_no_temp_low(d_36);
				logger.debug("主机............................................................");
				out.write(bmi);
			} else {
				logger.debug("从机............................................................");
				BmuSlaveInfo bsi = new BmuSlaveInfo();
				// MODEM_ID
				bsi.setModem_id(ctx.getModemId());
				// 数据日期
				bsi.setData_create_time(new Date());
				// 从机号
				bsi.setSid(sid);
				// 数据版本
				byte V = ctx.data.get();
				bsi.setData_version(V);
				// 模块监控电池数目(N)
				byte N = ctx.data.get();
				bsi.setBattery_count(N);
				// 模块监控温感数目(M)
				byte M = ctx.data.get();
				bsi.setTemp_sensor_count(M);
				// 模块监控均衡电源个数
				byte K = ctx.data.get();
				bsi.setBalanced_series_count(K);

				logger.debug("从机SID={}", sid & 0xff);
				logger.debug("数据版本号={}", V == 0x01 ? "V1.01" : "N/A");
				logger.debug("模块监控电池数目(N)={}串", N);
				logger.debug("模块监控温感数目(M)={}串", M);
				logger.debug("模块均衡电流数目(K)={}个", K);

				// 电池电压
				List<BmuSlaveBatteryState> stateList = new ArrayList<BmuSlaveBatteryState>();
				BmuSlaveBatteryState state = null;
				for (byte n = 1; n <= N; n++) {
					state = new BmuSlaveBatteryState();
					state.setModem_id(ctx.getModemId());
					state.setSid(sid);
					state.setBattery_no(n);
					int v = ctx.data.getUnsignedShort();
					state.setBattery_voltage(v == 0xffff ? null : v);
					logger.debug("第{}节电池电压:{}", n, state.getBattery_voltage());
					stateList.add(state);
				}
				bsi.setStateList(stateList);

				// 箱体温度
				List<BmuSlaveBoxTemp> tempList = new ArrayList<BmuSlaveBoxTemp>();
				BmuSlaveBoxTemp temp = null;
				for (byte m = 1; m <= M; m++) {
					temp = new BmuSlaveBoxTemp();
					temp.setModem_id(ctx.getModemId());
					temp.setSid(sid);
					temp.setBox_no(m);
					short t = ctx.data.getUnsigned();
					temp.setBox_temp(t == 0xff ? null : (short) (t - 40));
					logger.debug("第{}号箱体温度(偏移)={},(真实)={}℃", new Object[] { m, t, temp.getBox_temp() });
					tempList.add(temp);
				}
				bsi.setTempList(tempList);
				
				//N * 2 + M + 4
				byte H = ctx.data.get();
				String sH = ByteUtils.getEightBitsStringFromByte(H);
				// logger.debug("...H={}, sH={}", H, sH);
				logger.debug("H.bit0={}-{}", sH.charAt(7), new String[] { "加热未开启", "加热开启" }[sH.charAt(7) - 48]);
				logger.debug("H.bit1={}-{}", sH.charAt(6), new String[] { "风扇未开启", "风扇开启" }[sH.charAt(6) - 48]);
				// 热管理状态
				bsi.setHot_manage_state(H);

				// 均衡电流
				List<BmuSlaveBalanceCurrent> currentList = new ArrayList<BmuSlaveBalanceCurrent>();
				BmuSlaveBalanceCurrent current = null;
				for (byte k = 1; k <= K; k++) {
					current = new BmuSlaveBalanceCurrent();
					current.setModem_id(ctx.getModemId());
					current.setSid(sid);
					current.setSerial_no(k);
					int c = ctx.data.getUnsignedShort();
					current.setSerial_current(c == 0xffff ? null : c);
					logger.debug("第{}号电池均衡电流:{}", k, current.getSerial_current());
					currentList.add(current);
				}
				bsi.setCurrentList(currentList);

				// byte[] states = new byte[N]; // 电池均衡状态
				byte[] states = new byte[N];
				int ii = 0;
				for (int x = N * 2 + M + K * 2 + 5; x <= N * 2 + M + K * 2 + (N + 7) / 8 + 4; x++) {
					Byte b = ctx.data.get(x);
					String bits = ByteUtils.getEightBitsStringFromByte(b);
					for (int y = 7; y >= 0; y--) {
						if (ii == N)
							break;
						states[ii] = ByteUtils.charToByte(bits.charAt(y));
						logger.debug("OOOOO{}:={}", ii + 1, states[ii]);
						ii++;
					}
				}

				// 电池均衡状态
				int jj = 0;
				for (BmuSlaveBatteryState bsbs : stateList) {
					bsbs.setBalance_state(states[jj]);
					logger.debug("{}节电池均衡状态:{}", jj + 1, bsbs.getBalance_state());
					jj++;
				}
				logger.debug("从机............................................................");
				out.write(bsi);
			}
			ctx.reset();
			return true;
		}// end while
		return false;
	}

	private Context getContext(IoSession session) {
		Context context = (Context) session.getAttribute(CONTEXT);
		if (context == null) {
			context = new Context();
			session.setAttribute(CONTEXT, context);
		}
		return context;
	}

	private class Context {
		private boolean firstFrame = true;
		private String startCode;
		private int length;
		private Integer modemId;
		private IoBuffer data = IoBuffer.allocate(256).setAutoExpand(true);
		private IoBuffer in = IoBuffer.allocate(256).setAutoExpand(true);
		private IoBuffer crc = IoBuffer.allocate(256).setAutoExpand(true);

		public Context() {

		}

		public String getStartCode() {
			return startCode;
		}

		public void setStartCode(String startCode) {
			this.startCode = startCode;
		}

		public int getLength() {
			return length;
		}

		public void setLength(int length) {
			this.length = length;
		}

		public void reset() {
			startCode = null;
			length = 0;
			in.clear();
			data.clear();
		}

		public Integer getModemId() {
			return modemId;
		}

		public void setModemId(Integer modemId) {
			this.modemId = modemId;
		}

		public boolean isFirstFrame() {
			return firstFrame;
		}

		public void setFirstFrame(boolean firstFrame) {
			this.firstFrame = firstFrame;
		}

	}
}
